package microcredit.model.bo;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.PersistenceException;
import javax.persistence.TypedQuery;

import microcredit.model.Member;
import microcredit.model.MemberToTrainingType;
import microcredit.model.TrainingType;
import microcredit.model.ValidationException;

public class BOTraining extends BOBase {

	public void deleteTraining(TrainingType type) {
		try {
			open();
			type = manager.merge(type);
			manager.remove(type);
			commit();
		} catch (PersistenceException pE) {
			if (handleIntegrity(pE)) {
				throw new ValidationException("Could not delete training. Members are already assigned to this training.", pE);
			}
		} finally {
			close();
		}
	}
	
	public TrainingType refresh(TrainingType type) {
		if (type.getId() == null) {
			return type;
		}
		
		try {
			open();
			TypedQuery<TrainingType> q = manager.createQuery("SELECT t FROM TrainingType t WHERE t.id=:id", TrainingType.class);
			q.setParameter("id", type.getId());
			return q.getSingleResult();
		} finally {
			close();
		}
		
	}
	
	public List<MemberToTrainingType> readTrainingsByMember(Long memberID) {
		try {
			open();
			TypedQuery<MemberToTrainingType> q = manager.createQuery(
					" SELECT m FROM MemberToTrainingType m left join fetch m.member" +
					" where m.memberID =:mID ", MemberToTrainingType.class);
			q.setParameter("mID", memberID);
			List<MemberToTrainingType> result = q.getResultList();
			commit();
			return result;
		} finally {
			close();
		}
		
	}
	
	public List<MemberToTrainingType> readMembersByTrainingType(Long trainingID) {
		try {
			open();
			TypedQuery<MemberToTrainingType> q = manager.createQuery(
					" SELECT m FROM MemberToTrainingType m left join fetch m.member" +
					" where m.trainingTypeID =:tID ", MemberToTrainingType.class);
			q.setParameter("tID", trainingID);
			List<MemberToTrainingType> result = q.getResultList();
			commit();
			return result;
		} finally {
			close();
		}
	}
	
	public List<TrainingType> readTrainings() {
		try {
			open();
			TypedQuery<TrainingType> q = manager.createQuery("SELECT t FROM TrainingType t", TrainingType.class);
			List<TrainingType> trainings = q.getResultList();
			return trainings;
		} finally {
			close();
		}
	}
	
	public TrainingType saveTraining(TrainingType trainingType) {
		trainingType.validate();
		
		open();
		try {
			validateShortName(trainingType);
			validateMaximumMembers(trainingType);
			
			trainingType = manager.merge(trainingType);
			
			commit();
		} finally {
			close();
		}
		return trainingType;
	}

	private void validateMaximumMembers(TrainingType trainingType) {
		// if not saved yet not necessary to validate maximum members
		if (trainingType.getId() == null || trainingType.getMaximumMembers() == null) {
			return;
		}
		
		TypedQuery<Long> query = manager.createQuery("SELECT count(t) FROM MemberToTrainingType t WHERE " +
					" t.trainingTypeID = :id ", Long.class);
		
		query.setParameter("id", trainingType.getId());

		long count = query.getSingleResult();
		if (count > trainingType.getMaximumMembers()) {
			throw new ValidationException("The training type '"+trainingType.getShortName()+"' has exceeded the maximum amount of members assignable ("+trainingType.getMaximumMembers()+")", TrainingType.MAXIMUM_MEMBERS);
		}
	}
	
	private void validateShortName(TrainingType trainingType)
			throws ValidationException {
		TypedQuery<Long> query;
		if (trainingType.getId() == null) {
			query = manager.createQuery("SELECT count(t) FROM TrainingType t WHERE " +
					" t.shortName=:shortName", Long.class);			
		} else {
			query = manager.createQuery("SELECT count(t) FROM TrainingType t WHERE " +
					" t.shortName=:shortName AND t.id <> :id ", Long.class);
			query.setParameter("id", trainingType.getId());
		}
		query.setParameter("shortName", trainingType.getShortName());

		long count = query.getSingleResult();
		if (count > 0) {
			throw new ValidationException("The short name '"+trainingType.getShortName()+"' is already used by another training type.", TrainingType.SHORT_NAME);
		}
	}
	
	
	public static BOTraining create() {
		return new BOTraining();
	}

	public void deleteMemberToTraining(MemberToTrainingType training) {
		open();
		try {
			training = manager.merge(training);
			manager.remove(training);
			commit();
		} finally {
			close();
		}
	}
	
	public MemberToTrainingType saveMemberToTraining(MemberToTrainingType training) {
		open();
		try {
			training = manager.merge(training);
			commit();
		} finally {
			close();
		}
		return training;
	}
	
	public void assignMembersToTrainingType(List<Member> members,
			MemberToTrainingType type) {
		if (type.getTrainingType() == null) {
			throw new ValidationException("No training type defined.");
		}
		
		if (type.getTrainingType().getId() == null) {
			throw new ValidationException("The training type needs to be saved before assigning members to it.");
		}
		open();
		try {
			for (Member member : members) {
				List<MemberToTrainingType> types = member.getMemberToTrainingTypes();
				if (types == null) {
					types = new ArrayList<MemberToTrainingType>();
					member.setMemberToTrainingTypes(types);
				}
				MemberToTrainingType t = new MemberToTrainingType();
				t.setDate(type.getDate());
				t.setStatus(type.getStatus());
				t.setMember(member);
				t.setTrainingType(type.getTrainingType());
				t.setSequenceID(types.size()+0L);
				
				types.add(t);
				manager.merge(t);
			}
			flush();
			
			validateMaximumMembers(type.getTrainingType());
			
			commit();
		} finally {
			close();
		}
	}
	
}
